Skip to content

fix: .env loading no longer overrides already-set environment variables - #138

Open
mattpodwysocki wants to merge 3 commits into
mainfrom
fix/dotenv-no-override
Open

fix: .env loading no longer overrides already-set environment variables#138
mattpodwysocki wants to merge 3 commits into
mainfrom
fix/dotenv-no-override

Conversation

@mattpodwysocki

Copy link
Copy Markdown
Contributor

Summary

  • .env loading previously applied every key onto process.env with override semantics, so a .env file present in the working directory could take precedence over variables already set by the host process (e.g. MAPBOX_API_ENDPOINT, MAPBOX_ACCESS_TOKEN).
  • Extracted the loading logic into src/utils/loadDotEnv.ts and changed it to skip any key that's already set, matching the intent of Node's own process.loadEnvFile(). Already-set keys are now reported in the startup log message and the config.load_env tracing span instead of being silently skipped.
  • Matches the equivalent fix in mapbox/mcp-server.

Test plan

  • npx vitest run — all tests pass, including new regression tests in test/utils/loadDotEnv.test.ts covering override precedence
  • npm run build succeeds
  • Live-verified against the real built server with a real MCP client: with a .env in the working directory pointing MAPBOX_API_ENDPOINT elsewhere, and the host already having set both variables, a real tool call (list_styles_tool) still reached the host-configured api.mapbox.com, not the .env-supplied value
  • CHANGELOG.md updated

🤖 Generated with Claude Code

.env loading previously applied every key onto process.env with override
semantics, so a .env file present in the working directory could take
precedence over variables already set by the host process (e.g.
MAPBOX_API_ENDPOINT, MAPBOX_ACCESS_TOKEN).

Extracted the loading logic into src/utils/loadDotEnv.ts and changed it to
skip any key that's already set, matching the intent of Node's own
process.loadEnvFile(). Already-set keys are now reported in the startup
log message and the config.load_env tracing span instead of being
silently skipped.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
@mattpodwysocki
mattpodwysocki requested a review from a team as a code owner August 25, 2026 16:11
mattpodwysocki and others added 2 commits August 26, 2026 12:49
Reviewer feedback on this PR (Mofei) pointed out a real gap in the prior
fix: skipping .env keys only when already set doesn't help when the host
never set MAPBOX_API_ENDPOINT in the first place -- the common case, since
most operators only set MAPBOX_ACCESS_TOKEN and rely on the built-in
https://api.mapbox.com/ default. In that case a malicious .env could still
set MAPBOX_API_ENDPOINT, since "already set" was false, and the real
host-injected access token would still be sent to that endpoint.

loadDotEnv now takes a set of protectedKeys that .env may never set at
all, regardless of whether the target env already has a value for them.
index.ts passes MAPBOX_ACCESS_TOKEN/MAPBOX_API_ENDPOINT. Confirmed live
both ways: before this change, a tool call with a malicious .env and no
host-set MAPBOX_API_ENDPOINT reached the attacker-controlled endpoint;
after it, the same call reaches the real API.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
@mattpodwysocki

Copy link
Copy Markdown
Contributor Author

Update: pushed a fix for the gap Mofei flagged (correctly) in Slack.

The original fix only skipped .env keys that were already set in the process environment. That doesn't help when the host never set MAPBOX_API_ENDPOINT in the first place — the common case, since most operators only set MAPBOX_ACCESS_TOKEN and rely on the built-in https://api.mapbox.com/ default. In that case a malicious .env could still set MAPBOX_API_ENDPOINT, since it was never "already set" to begin with, and the real host-injected access token would still be sent to that attacker-controlled endpoint.

Fixed by making MAPBOX_ACCESS_TOKEN/MAPBOX_API_ENDPOINT off-limits to .env entirely — loadDotEnv now takes a protectedKeys set that .env may never populate, regardless of whether the target env already has a value. A custom endpoint (e.g. an enterprise proxy) still works, but has to come from a real environment variable at the host/launch-config level, not a project-local .env.

Confirmed live both ways: with the prior fix, a tool call with the malicious .env and no host-set MAPBOX_API_ENDPOINT reached the attacker-controlled host (fetch failed against a non-resolving domain); with this fix, the same call reaches the real API (Not Authorized - Invalid Token, the expected response for a fake token).

New regression tests cover this exact scenario in test/utils/loadDotEnv.test.ts.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant